"""BLE connection management module."""

import logging
import time
import threading
from enum import Enum
from typing import Optional, Dict, Any, Callable
from datetime import datetime, timedelta

from src.ble.constants import (
    MAX_CONNECTIONS,
    CONNECTION_TIMEOUT,
    IDLE_TIMEOUT,
    STATUS_SUCCESS,
    STATUS_CONNECTION_LIMIT,
    STATUS_TIMEOUT
)

logger = logging.getLogger(__name__)


class ConnectionState(Enum):
    """BLE connection states."""
    IDLE = "idle"
    ADVERTISING = "advertising"
    CONNECTED = "connected"
    CONFIGURING = "configuring"
    DISCONNECTING = "disconnecting"


class Connection:
    """Represents a single BLE connection."""
    
    def __init__(self, device_path: str, device_address: str):
        """
        Initialize a connection.
        
        Args:
            device_path: DBus path of the connected device
            device_address: Bluetooth address of the device
        """
        self.device_path = device_path
        self.device_address = device_address
        self.connected_at = datetime.now()
        self.last_activity = datetime.now()
        self.mtu = 23  # Default BLE MTU
        self.state = ConnectionState.CONNECTED
        self.metadata: Dict[str, Any] = {}
        
    def update_activity(self):
        """Update last activity timestamp."""
        self.last_activity = datetime.now()
        
    def is_timeout(self) -> bool:
        """Check if connection has timed out."""
        idle_time = (datetime.now() - self.last_activity).total_seconds()
        total_time = (datetime.now() - self.connected_at).total_seconds()
        
        return (idle_time > IDLE_TIMEOUT or 
                total_time > CONNECTION_TIMEOUT)
                
    def get_connection_info(self) -> Dict[str, Any]:
        """Get connection information."""
        return {
            "device_address": self.device_address,
            "device_path": self.device_path,
            "connected_at": self.connected_at.isoformat(),
            "last_activity": self.last_activity.isoformat(),
            "mtu": self.mtu,
            "state": self.state.value,
            "metadata": self.metadata
        }


class ConnectionManager:
    """Manages BLE connections and enforces connection limits."""
    
    def __init__(self):
        """Initialize connection manager."""
        self._connections: Dict[str, Connection] = {}
        self._state = ConnectionState.IDLE
        self._lock = threading.RLock()
        self._state_callbacks: Dict[ConnectionState, list] = {}
        self._timeout_thread: Optional[threading.Thread] = None
        self._running = False
        
    def start(self):
        """Start the connection manager."""
        with self._lock:
            self._running = True
            self._start_timeout_monitor()
            self.set_state(ConnectionState.IDLE)
            logger.info("Connection manager started")
            
    def stop(self):
        """Stop the connection manager."""
        with self._lock:
            self._running = False
            if self._timeout_thread and self._timeout_thread.is_alive():
                self._timeout_thread.join(timeout=2)
            self._connections.clear()
            self._state = ConnectionState.IDLE
            logger.info("Connection manager stopped")
            
    def _start_timeout_monitor(self):
        """Start timeout monitoring thread."""
        if not self._timeout_thread or not self._timeout_thread.is_alive():
            self._timeout_thread = threading.Thread(
                target=self._monitor_timeouts,
                daemon=True
            )
            self._timeout_thread.start()
            
    def _monitor_timeouts(self):
        """Monitor connections for timeouts."""
        while self._running:
            try:
                with self._lock:
                    timed_out = []
                    for device_path, conn in self._connections.items():
                        if conn.is_timeout():
                            timed_out.append(device_path)
                            
                    for device_path in timed_out:
                        logger.warning(f"Connection timeout for {device_path}")
                        self.disconnect(device_path, reason="timeout")
                        
            except Exception as e:
                logger.error(f"Error in timeout monitor: {e}")
                
            time.sleep(5)  # Check every 5 seconds
            
    def get_state(self) -> ConnectionState:
        """Get current connection state."""
        with self._lock:
            return self._state
            
    def set_state(self, state: ConnectionState):
        """
        Set connection state and trigger callbacks.
        
        Args:
            state: New connection state
        """
        with self._lock:
            if self._state != state:
                old_state = self._state
                self._state = state
                logger.info(f"State changed: {old_state.value} -> {state.value}")
                
                # Trigger state change callbacks
                if state in self._state_callbacks:
                    for callback in self._state_callbacks[state]:
                        try:
                            callback(old_state, state)
                        except Exception as e:
                            logger.error(f"State callback error: {e}")
                            
    def register_state_callback(self, state: ConnectionState, callback: Callable):
        """
        Register a callback for state changes.
        
        Args:
            state: State to trigger callback
            callback: Callback function(old_state, new_state)
        """
        with self._lock:
            if state not in self._state_callbacks:
                self._state_callbacks[state] = []
            self._state_callbacks[state].append(callback)
            
    def can_accept_connection(self) -> bool:
        """Check if new connections can be accepted."""
        with self._lock:
            return len(self._connections) < MAX_CONNECTIONS
            
    def connect(self, device_path: str, device_address: str) -> tuple[bool, str]:
        """
        Handle new connection.
        
        Args:
            device_path: DBus path of the device
            device_address: Bluetooth address
            
        Returns:
            Tuple of (success, message)
        """
        with self._lock:
            if not self.can_accept_connection():
                logger.warning(f"Connection rejected for {device_address}: limit reached")
                return False, "Connection limit reached"
                
            if device_path in self._connections:
                logger.info(f"Device {device_address} already connected")
                return True, "Already connected"
                
            conn = Connection(device_path, device_address)
            self._connections[device_path] = conn
            self.set_state(ConnectionState.CONNECTED)
            
            logger.info(f"Device connected: {device_address}")
            return True, "Connected successfully"
            
    def disconnect(self, device_path: str, reason: str = "requested") -> bool:
        """
        Handle disconnection.
        
        Args:
            device_path: DBus path of the device
            reason: Reason for disconnection
            
        Returns:
            True if disconnected, False if not found
        """
        with self._lock:
            if device_path not in self._connections:
                return False
                
            conn = self._connections[device_path]
            logger.info(f"Disconnecting device {conn.device_address}: {reason}")
            
            del self._connections[device_path]
            
            # Update state
            if len(self._connections) == 0:
                self.set_state(ConnectionState.IDLE)
                
            return True
            
    def get_connection(self, device_path: str) -> Optional[Connection]:
        """Get connection by device path."""
        with self._lock:
            return self._connections.get(device_path)
            
    def get_all_connections(self) -> Dict[str, Connection]:
        """Get all active connections."""
        with self._lock:
            return self._connections.copy()
            
    def update_mtu(self, device_path: str, mtu: int) -> bool:
        """
        Update MTU for a connection.
        
        Args:
            device_path: Device path
            mtu: New MTU value
            
        Returns:
            True if updated, False if connection not found
        """
        with self._lock:
            conn = self._connections.get(device_path)
            if conn:
                conn.mtu = mtu
                conn.update_activity()
                logger.info(f"MTU updated to {mtu} for {conn.device_address}")
                return True
            return False
            
    def update_activity(self, device_path: str):
        """Update activity timestamp for a connection."""
        with self._lock:
            conn = self._connections.get(device_path)
            if conn:
                conn.update_activity()
                
    def is_connected(self, device_path: str) -> bool:
        """Check if device is connected."""
        with self._lock:
            return device_path in self._connections
            
    def get_connection_count(self) -> int:
        """Get number of active connections."""
        with self._lock:
            return len(self._connections)
            
    def get_status(self) -> Dict[str, Any]:
        """Get connection manager status."""
        with self._lock:
            connections_info = [
                conn.get_connection_info() 
                for conn in self._connections.values()
            ]
            
            return {
                "state": self._state.value,
                "connection_count": len(self._connections),
                "max_connections": MAX_CONNECTIONS,
                "can_accept": self.can_accept_connection(),
                "connections": connections_info
            }